package com.hua.utility.utility;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class UtilNumber {

	public static final String className = UtilNumber.class.getName();

	public static final String[] ALLOW_TYPES = { "BigDecimal", "BigInteger", "AtomicInteger", "AtomicLong", "Byte",
			"Short", "Integer", "Long", "Float", "Double", "Number" };

	public static final Integer[] ROUNDING_MODE_BIGDECIMAL = { BigDecimal.ROUND_UP, BigDecimal.ROUND_DOWN,
			BigDecimal.ROUND_CEILING, BigDecimal.ROUND_FLOOR, BigDecimal.ROUND_HALF_UP, BigDecimal.ROUND_HALF_DOWN,
			BigDecimal.ROUND_HALF_EVEN, BigDecimal.ROUND_UNNECESSARY };

	public static final java.util.Locale LOCALE_DEFAULT = java.util.Locale.CHINA;

	public static final int ROUNDINGMODE_DEFAULT = BigDecimal.ROUND_HALF_UP;

	private static final int SCALE_DEFAULT = 2;

	@SuppressWarnings("rawtypes")
	private static boolean isEmpty(Object o) {
		Boolean checkResult = false;
		if (o == null) {
			checkResult = true;
		} else if (o instanceof String) {
			if ((o.toString().trim()).length() <= 0) {
				checkResult = true;
			}
		} else if (o instanceof java.util.Collection) {
			if (((java.util.Collection) o).size() <= 0) {
				checkResult = true;
			}
		} else if (o instanceof java.util.Map) {
			if (((java.util.Map) o).size() <= 0) {
				checkResult = true;
			}
		} else if (o instanceof java.lang.CharSequence) {
			if (((java.lang.CharSequence) o).length() <= 0) {
				checkResult = true;
			}
		} else {
			try {
				o.toString();
			} catch (Exception e) {
				throwRuntimeException(o.getClass().getName() + "toString forbidden operation.");
			}
		}
		return checkResult;
	}

	private static void throwRuntimeException(Object o) {
		if (isEmpty(o)) {
			return;
		} else if (o instanceof Throwable) {
			throw new RuntimeException(className + ":" + ((Throwable) o).getMessage());
		} else {
			throw new RuntimeException(className + ":" + o.toString());
		}
	}

	public static int getRoundingModeByString(String value) {
		if (isEmpty(null)) {
			return ROUNDINGMODE_DEFAULT;
		} else {
			value = value.trim();
			if ("ROUND_HALF_UP".equals(value))
				return BigDecimal.ROUND_HALF_UP;
			else if ("ROUND_HALF_DOWN".equals(value))
				return BigDecimal.ROUND_HALF_DOWN;
			else if ("ROUND_HALF_EVEN".equals(value))
				return BigDecimal.ROUND_HALF_EVEN;
			else if ("ROUND_UP".equals(value))
				return BigDecimal.ROUND_UP;
			else if ("ROUND_DOWN".equals(value))
				return BigDecimal.ROUND_DOWN;
			else if ("ROUND_CEILING".equals(value))
				return BigDecimal.ROUND_CEILING;
			else if ("ROUND_FLOOR".equals(value))
				return BigDecimal.ROUND_FLOOR;
			else if ("ROUND_UNNECCESSARY".equals(value))
				return BigDecimal.ROUND_UNNECESSARY;
			else
				return ROUNDINGMODE_DEFAULT;
		}
	}

	private static BigDecimal getNumber(Number number) {
		BigDecimal result = null;
		if (isEmpty(number)) {
			throwRuntimeException("object is null.");
		} else if (number instanceof BigDecimal) {
			result = (BigDecimal) number;
		} else if (number instanceof BigInteger) {
			result = new BigDecimal(((BigInteger) number).doubleValue());
		} else if (number instanceof AtomicInteger) {
			result = new BigDecimal(((AtomicInteger) number).doubleValue());
		} else if (number instanceof AtomicLong) {
			result = new BigDecimal(((AtomicLong) number).doubleValue());
		} else if (number instanceof Byte) {
			result = new BigDecimal(((Byte) number).doubleValue());
		} else if (number instanceof Short) {
			result = new BigDecimal(((Short) number).doubleValue());
		} else if (number instanceof Integer) {
			result = new BigDecimal(((Integer) number).doubleValue());
		} else if (number instanceof Long) {
			result = new BigDecimal(((Long) number).doubleValue());
		} else if (number instanceof Float) {
			result = new BigDecimal(((Float) number).doubleValue());
		} else if (number instanceof Double) {
			result = new BigDecimal(((Double) number).doubleValue());
		} else if (number instanceof Number) {
			result = new BigDecimal(((Number) number).doubleValue());
		} else {
			throwRuntimeException("allow type Number and the Subclass");
		}
		return result;
	}

	public static boolean checkNumberRange(Number number, Number min, Number max, boolean beforeInclude,
			boolean afterInclude) {
		boolean result = true;
		BigDecimal oBigDecimal = getNumber(number);
		if (Double.isInfinite(min.doubleValue()) || Double.isNaN(min.doubleValue())) {
			min = null;
		}
		if (Double.isInfinite(max.doubleValue()) || Double.isNaN(max.doubleValue())) {
			max = null;
		}
		if (!isEmpty(min)) {
			BigDecimal minBigDecimal = getNumber(min);
			if (oBigDecimal.compareTo(minBigDecimal) < (beforeInclude ? 0 : 1)) {
				result = false;
			}
		}
		if (!isEmpty(max)) {
			BigDecimal maxBigDecimal = getNumber(max);
			if (oBigDecimal.compareTo(maxBigDecimal) > (afterInclude ? 0 : -1)) {
				result = false;
			}
		}
		return result;
	}

	@SuppressWarnings("rawtypes")
	public static Double getMaxValue(Class clazz) {
		Number result = null;
		if (isEmpty(clazz)) {
			result = Double.POSITIVE_INFINITY;
		} else if (clazz.equals(BigDecimal.class)) {
			result = Double.POSITIVE_INFINITY;
		} else if (clazz.equals(BigInteger.class)) {
			result = Double.POSITIVE_INFINITY;
		} else if (clazz.equals(AtomicInteger.class)) {
			result = Integer.MAX_VALUE;
		} else if (clazz.equals(AtomicLong.class)) {
			result = Long.MAX_VALUE;
		} else if (clazz.equals(Byte.class)) {
			result = Byte.MAX_VALUE;
		} else if (clazz.equals(Short.class)) {
			result = Short.MAX_VALUE;
		} else if (clazz.equals(Integer.class)) {
			result = Integer.MAX_VALUE;
		} else if (clazz.equals(Long.class)) {
			result = Long.MAX_VALUE;
		} else if (clazz.equals(Float.class)) {
			result = Float.MAX_VALUE;
		} else if (clazz.equals(Double.class)) {
			result = Double.MAX_VALUE;
		} else if (clazz.equals(Number.class)) {
			result = Double.POSITIVE_INFINITY;
		} else {
			if (!clazz.getSuperclass().equals(Number.class)) {
				result = getMaxValue(clazz.getSuperclass());
			} else {
				throwRuntimeException("allow type Number and the Subclass");
			}
		}
		return result.doubleValue();
	}

	@SuppressWarnings("rawtypes")
	public static Double getMinValue(Class clazz) {
		Number result = null;
		if (isEmpty(clazz)) {
			result = Double.NEGATIVE_INFINITY;
		} else if (clazz.equals(BigDecimal.class)) {
			result = Double.NEGATIVE_INFINITY;
		} else if (clazz.equals(BigInteger.class)) {
			result = Double.NEGATIVE_INFINITY;
		} else if (clazz.equals(AtomicInteger.class)) {
			result = Integer.MIN_VALUE;
		} else if (clazz.equals(AtomicLong.class)) {
			result = Long.MIN_VALUE;
		} else if (clazz.equals(Byte.class)) {
			result = Byte.MIN_VALUE;
		} else if (clazz.equals(Short.class)) {
			result = Short.MIN_VALUE;
		} else if (clazz.equals(Integer.class)) {
			result = Integer.MIN_VALUE;
		} else if (clazz.equals(Long.class)) {
			result = Long.MIN_VALUE;
		} else if (clazz.equals(Float.class)) {
			result = Float.MIN_VALUE;
		} else if (clazz.equals(Double.class)) {
			result = Double.MIN_VALUE;
		} else if (clazz.equals(Number.class)) {
			result = Double.NEGATIVE_INFINITY;
		} else {
			if (!clazz.getSuperclass().equals(Number.class)) {
				result = getMinValue(clazz.getSuperclass());
			} else {
				throwRuntimeException("allow type Number and the Subclass");
			}
		}
		return result.doubleValue();
	}

	@SuppressWarnings("rawtypes")
	public static Number convertToOtherClass(Number number, Class clazz) {
		Number result = null;
		BigDecimal target = getNumber(number);
		if (isEmpty(clazz)) {
			clazz = BigDecimal.class;
		}
		Double min = getMinValue(clazz);
		Double max = getMaxValue(clazz);
		if (!checkNumberRange(target, min, max, true, true)) {
			String minStr = "NEGATIVE_INFINITY";
			String maxStr = "POSITIVE_INFINITY";
			if (!Double.isInfinite(min.doubleValue()) && !Double.isNaN(min.doubleValue())) {
				minStr = String.valueOf(min.doubleValue());
			}
			if (!Double.isInfinite(max.doubleValue()) && !Double.isNaN(max.doubleValue())) {
				maxStr = String.valueOf(max.doubleValue());
			}
			throwRuntimeException("the " + clazz.getName() + " class allow the range [" + minStr + "," + maxStr + "]");
		}
		if (clazz.equals(BigDecimal.class)) {
			result = target;
		} else if (clazz.equals(BigInteger.class)) {
			result = BigInteger.valueOf(target.longValue());
		} else if (clazz.equals(AtomicInteger.class)) {
			result = new AtomicInteger(target.intValue());
		} else if (clazz.equals(AtomicLong.class)) {
			result = new AtomicLong(target.longValue());
		} else if (clazz.equals(Byte.class)) {
			result = new Byte(target.intValue() + "");
		} else if (clazz.equals(Short.class)) {
			result = new Short(target.intValue() + "");
		} else if (clazz.equals(Integer.class)) {
			result = new Integer(target.intValue());
		} else if (clazz.equals(Long.class)) {
			result = new Long(target.longValue());
		} else if (clazz.equals(Float.class)) {
			result = new Float(target.doubleValue());
		} else if (clazz.equals(Double.class)) {
			result = new Double(target.doubleValue());
		} else if (clazz.equals(Number.class)) {
			result = target;
		} else {
			if (!clazz.getSuperclass().equals(Number.class)) {
				result = convertToOtherClass(number, clazz.getSuperclass());
			} else {
				throwRuntimeException("allow type Number and the Subclass");
			}
		}
		return result;
	}

	public static BigDecimal getBigDecimal(Number number) {
		return (BigDecimal) convertToOtherClass(number, BigDecimal.class);
	}

	public static Double getDouble(Number number) {
		return (Double) convertToOtherClass(number, Double.class);
	}

	public static Long getLong(Number number) {
		return (Long) convertToOtherClass(number, Long.class);
	}

	public static Integer getInteger(Number number) {
		return (Integer) convertToOtherClass(number, Integer.class);
	}

	public static String formatNumberToPercent(Number number) {
		return formatNumberToPercent(number, null, null);
	}

	public static String formatNumberToPercent(Number number, int scale) {
		return formatNumberToPercent(number, scale, null);
	}

	public static String formatNumberToPercent(Number number, Integer scale, Integer roundingMode) {
		if (isEmpty(number)) {
			throwRuntimeException("object is null.");
		}
		if (scale < 0) {
			scale = SCALE_DEFAULT;
		}
		if (roundingMode < 0 || roundingMode > 7) {
			roundingMode = ROUNDINGMODE_DEFAULT;
		}
		BigDecimal bigDecimal = getBigDecimal(number);
		bigDecimal = bigDecimal.multiply(new BigDecimal(100.0)).setScale(scale, roundingMode);
		return (bigDecimal.toString() + "%");
	}

	public static String formatNumberToFixed(Number number) {
		return formatNumberToFixed(number, null, null);
	}

	public static String formatNumberToFixed(Number number, int scale) {
		return formatNumberToFixed(number, scale, null);
	}

	public static String formatNumberToFixed(Number number, Integer scale, Integer roundingMode) {
		if (isEmpty(number)) {
			throwRuntimeException("object is null.");
		}
		if (scale < 0) {
			scale = SCALE_DEFAULT;
		}
		if (roundingMode < 0 || roundingMode > 7) {
			roundingMode = ROUNDINGMODE_DEFAULT;
		}
		BigDecimal bigDecimal = getBigDecimal(number);
		bigDecimal = bigDecimal.setScale(scale, roundingMode);
		return (bigDecimal.toString());
	}

	/*
	 * 符号 位置 本地化 含义: 0 数字 是 阿拉伯数字 ；# 数字 是 阿拉伯数字，如果不存在则显示为 0 ； . 数字 是
	 * 小数分隔符或货币小数分隔符 ； - 数字 是 减号 , 数字 是 分组分隔符 E 数字 是
	 * 分隔科学计数法中的尾数和指数。在前缀或后缀中无需加引号。 ; 子模式边界 是 分隔正数和负数子模式 % 前缀或后缀 是 乘以 100
	 * 并显示为百分数 \u2030 前缀或后缀 是 乘以 1000 并显示为千分数 \u00A4 前缀或后缀 否
	 * 货币记号，由货币符号替换。如果两个同时出现，则用国际货币符号替换。如果出现在某个模式中，则使用货币小数分隔符，而不使用小数分隔符。 前缀或后缀 否
	 * 用于在前缀或或后缀中为特殊字符加引号， 例如 "'#'#" 将 123 格式化为 "#123"。要创建单引号本身，请连续使用两个单引号：
	 * "# o''clock"。
	 */
	public static String formatNumberToString(Number number, String formatString) {
		return formatNumberToString(number, formatString, null);
	}

	public static String formatNumberToString(Number number, String formatString, DecimalFormatSymbols symbols) {
		if (isEmpty(number)) {
			throwRuntimeException("object is null.");
		}
		String result = "";
		DecimalFormat decimalFormat = new DecimalFormat();
		if (isEmpty(formatString) && isEmpty(symbols)) {
			result = number.toString();
		} else {
			if (!isEmpty(formatString)) {
				decimalFormat.applyPattern(formatString);
			}
			if (!isEmpty(symbols)) {
				decimalFormat.setDecimalFormatSymbols(symbols);
			}
			result = decimalFormat.format(number);
		}
		return result;
	}

	public static String formatNumberToString(Number number, NumberFormat numberFormat) {
		if (isEmpty(number)) {
			throwRuntimeException("object is null.");
		}
		String result = "";
		if (isEmpty(numberFormat)) {
			result = number.toString();
		} else {
			result = numberFormat.format(number);
		}
		return result;
	}

	public static BigDecimal parseStringToNumber(String numberString, NumberFormat numberFormat) {
		Number result = 0;
		if (!isEmpty(numberString)) {
			try {
				if (isEmpty(numberFormat)) {
					numberFormat = NumberFormat.getInstance();
				}
				result = numberFormat.parse(numberString);
			} catch (Exception e1) {
				try {
					result = new BigDecimal(numberString);
				} catch (Exception e2) {
					throw new RuntimeException(e1.toString());
				}
			}
		}
		return getBigDecimal(result);
	}

	public static int compareTo(Number number1, Number number2) {
		BigDecimal bigDecimal1 = getBigDecimal(number1);
		BigDecimal bigDecimal2 = getBigDecimal(number2);
		return bigDecimal1.compareTo(bigDecimal2);
	}

	public static boolean equalNumber(Number number1, Number number2) {
		boolean result = false;
		if (compareTo(number1, number2) == 0) {
			result = true;
		} else {
			result = false;
		}
		return result;
	}

	public static boolean equalStrict(Number number1, Number number2) {
		boolean result = false;
		if (number1.getClass() == number2.getClass()) {
			result = equalNumber(number1, number2);
		} else {
			result = false;
		}
		return result;
	}

	public static BigDecimal opAdd(Number number1, Number number2) {
		BigDecimal bigDecimal1 = getBigDecimal(number1);
		BigDecimal bigDecimal2 = getBigDecimal(number2);
		return bigDecimal1.add(bigDecimal2);
	}

	public static BigDecimal opSubtract(Number number1, Number number2) {
		BigDecimal bigDecimal1 = getBigDecimal(number1);
		BigDecimal bigDecimal2 = getBigDecimal(number2);
		return bigDecimal1.subtract(bigDecimal2);
	}

	public static BigDecimal opMultiply(Number number1, Number number2) {
		BigDecimal bigDecimal1 = getBigDecimal(number1);
		BigDecimal bigDecimal2 = getBigDecimal(number2);
		return bigDecimal1.multiply(bigDecimal2);
	}

	public static BigDecimal opDivide(Number number1, Number number2) {
		return opDivide(number1, number2, SCALE_DEFAULT);
	}

	public static BigDecimal opDivide(Number number1, Number number2, int scale) {
		if (scale < 0) {
			scale = SCALE_DEFAULT;
		}
		BigDecimal bigDecimal1 = getBigDecimal(number1);
		BigDecimal bigDecimal2 = getBigDecimal(number2);
		return bigDecimal1.divide(bigDecimal2, scale, ROUNDINGMODE_DEFAULT);
	}

	public static BigDecimal opRoundScale(Number number1, int scale) {
		if (scale < 0) {
			scale = SCALE_DEFAULT;
		}
		BigDecimal bigDecimal1 = getBigDecimal(number1);
		BigDecimal one = new BigDecimal("1");
		return bigDecimal1.divide(one, scale, ROUNDINGMODE_DEFAULT);
	}

	public static BigDecimal getMaxNumber(Number... numbers) {
		List<Number> numberList = Arrays.asList(numbers);
		return getMaxNumber(numberList);
	}

	public static BigDecimal getMaxNumber(List<Number> numberList) {
		if (isEmpty(numberList)) {
			throwRuntimeException("object is null.");
		}
		BigDecimal maxValue = getBigDecimal(numberList.get(0));
		for (int i = 1; i < numberList.size(); i++) {
			maxValue = maxValue.max(getBigDecimal(numberList.get(i)));
		}
		return maxValue;
	}

	public static BigDecimal getMinNumber(Number... numbers) {
		List<Number> numberList = Arrays.asList(numbers);
		return getMinNumber(numberList);
	}

	public static BigDecimal getMinNumber(List<Number> numberList) {
		if (isEmpty(numberList)) {
			throwRuntimeException("object is null.");
		}
		BigDecimal minValue = getBigDecimal(numberList.get(0));
		for (int i = 1; i < numberList.size(); i++) {
			minValue = minValue.min(getBigDecimal(numberList.get(i)));
		}
		return minValue;
	}

}
